package com.hup.joystick.view.joySetting;

import com.hup.joystick.config.AppConfig;
import com.hup.joystick.constant.DeviceConstant.ConnectType;
import com.hup.joystick.constant.DeviceKey.KeyType;
import com.hup.joystick.constant.DeviceKey.StandardKeys;
import com.hup.joystick.model.DeviceConfig;
import com.hup.joystick.model.KeyMapConfig;
import com.hup.joystick.model.joystick.NativeEvent;
import com.hup.joystick.model.joystick.NativeEvent.NativeAction;
import com.hup.joystick.model.joystick.NativeKey;
import com.hup.joystick.model.joystick.StandardEvent;
import com.hup.joystick.model.joystick.StandardEvent.StandardAction;
import com.hup.joystick.model.joystick.StandardKey;
import com.hup.joystick.mvp.JoySettingMvp.JoySettingPresenter;
import com.hup.joystick.service.jna.DInputHResultException.HResult;
import com.hup.joystick.service.joystick.JoyEventListener;
import com.hup.joystick.view.joySetting.KeyBindingItem.KeyBindingListener;
import com.hup.utils.commons.ListUtil;
import com.hup.utils.commons.TextUtil;
import com.hup.utils.commons.exception.AppCommonException;
import com.hup.utils.commons.interfaces.Runners.Runner1;
import com.hup.utils.commons.start.DialogHelper;
import com.hup.utils.commons.start.LookThemeUtil;
import com.hup.utils.commons.start.SpringContext;
import com.hup.utils.swing.ComponentUtil;
import com.hup.utils.swing.frame.BaseFrame;
import com.hup.utils.swing.frame.SingletonWindowHelper;
import lombok.extern.log4j.Log4j2;

import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.WindowEvent;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map.Entry;

/**
 * @author hugan
 * @date 2020/10/19
 */
@Log4j2
public class JoySettingFrame extends BaseFrame {
    public static final String TITLE = "手柄配置";

    private final JoySettingPresenter presenter;
    private final ViewItemEditHandler viewItemEditHandler = new ViewItemEditHandler();
    private final JoyEventHandler joyEventHandler = new JoyEventHandler();
    private JoySettingForm form;
    /**
     * 是否修改过配置
     */
    private boolean configNotSave;
    private DeviceConfig lastSelectedConfig;
    /**
     * 当前正在绑定按键的item
     */
    private KeyBindingItem currentBindingItem;

    public static void showFrame(Window parent) {
        JoySettingFrame window = SingletonWindowHelper.getWindow(JoySettingFrame.class);
        if (window == null) window = new JoySettingFrame(parent);
        window.setVisible(true);
    }

    private JoySettingFrame(Window parent) {
        super(TITLE);

        presenter = SpringContext.getBean(JoySettingPresenter.class);
        initView(parent);
        initListener();
        initModel();
    }

    private void initView(Window parent) {
        form = new JoySettingForm();
        setContentPane(form.root);

        form.cbDevice.setItemTextHandler(deviceConfig -> {
            StringBuilder sb = new StringBuilder(deviceConfig.name);
            if (!deviceConfig.online) sb.append("(离线)");
            if (deviceConfig.err != null) sb.append("(配置损坏)");
            if (deviceConfig.fromFile == null) sb.append("(未配置)");
            return sb.toString();
        });
        form.btnDelete.setVisible(false);
        form.btnSave.setEnabled(false);
        form.txtAppConfigInfo.setText("摇杆原始范围:[±" + AppConfig.STICK_RANGE + "]\n"
                + "状态刷新频率:" + AppConfig.STATE_SCAN_FPS + "fps");
        viewItemEditHandler.initKeyPanel();

        String typeTip = "目前只为标记项,无任何功能性作用";
        form.cbXInput.setToolTipText(typeTip);
        form.cbDInput.setToolTipText(typeTip);
        form.cbHid.setToolTipText(typeTip);

        LookThemeUtil.initFrameSize(this, 800, 500);
        setLocationRelativeTo(parent);
    }

    private void initListener() {
        form.cbDevice.addActionListener(e -> viewItemEditHandler.onSelectedDevice(form.cbDevice.getSelectedItem()));
        form.cbHid.addActionListener(e -> switchConfigEdited(true));
        form.cbDInput.addActionListener(e -> switchConfigEdited(true));
        form.cbXInput.addActionListener(e -> switchConfigEdited(true));
        form.btnRefresh.setActionListener(e -> {
            if (confirmNotSaveConfig()) return;
            initModel();
        });
        form.btnDelete.setActionListener(confirm("删除配置", () -> {
            DeviceConfig config = form.cbDevice.getSelectedItem();
            if (config != null && config.fromFile != null) {
                boolean delete = new File(config.fromFile).delete();
                log.info("delete={}", delete);
                switchConfigEdited(false);//处理:选择了放弃保存后,initModel加载数据时又有弹窗
                initModel();
            }
        }));
        form.btnAddKey.setActionListener(e -> runWithCatch("添加按键", viewItemEditHandler::addCustomKey));
        form.btnSave.addActionListener(e -> saveConfig());
        form.btnCancel.addActionListener(e -> {
            boolean close = onWindowClosing(null);
            if (close) dispose();
        });
        KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventPostProcessor(viewItemEditHandler);//全局监听Esc
    }

    private void initModel() {
        execute("初始化界面数据", presenter::getDevices, devices -> {
            DefaultComboBoxModel<DeviceConfig> model = new DefaultComboBoxModel<>(devices);
            /*
            选择优先级
            .已配置的设备:在线,离线
            .未配置的设备
             */
            DeviceConfig select = ListUtil.get(devices, device -> device.fromFile != null && device.online && device.err == null);//已配置,在线
            if (select == null) {
                select = ListUtil.get(devices, device -> device.fromFile != null && device.err == null);//在配置,离线
                if (select == null) select = ListUtil.get(devices, device -> device.online);//未配置,在线
            }
            if (select != null) model.setSelectedItem(select);
            form.cbDevice.setModel(model);
            viewItemEditHandler.onSelectedDevice(form.cbDevice.getSelectedItem());
        });
    }

    @Override
    public boolean onWindowClosing(WindowEvent e) {
        if (confirmNotSaveConfig()) return false;
        presenter.onWindowClosing();
        KeyboardFocusManager.getCurrentKeyboardFocusManager().removeKeyEventPostProcessor(viewItemEditHandler);
        return true;
    }

    private void switchConfigEdited(boolean edited) {
        if (lastSelectedConfig != null) {//有设备时才修改
            configNotSave = edited;
            form.btnSave.setEnabled(edited);
        }
    }

    /**
     * 判断有无修改配置,如果有则弹窗确认
     *
     * @return true-取消进行中的操作(不放弃保存);false-放弃保存(然后取消已编辑状态)
     */
    private boolean confirmNotSaveConfig() {
        //没有配置或没有修改配置:不取消
        if (lastSelectedConfig == null || !configNotSave) return false;
        //不是确认取消:不取消
        boolean notSave = DialogHelper.yesNo("放弃保存?").show(this);
        if (notSave) switchConfigEdited(false);
        return !notSave;
    }

    private void saveConfig() {
        if (currentBindingItem != null) {
            DialogHelper.confirm("按键绑定中").show(this);
            return;
        }
        if (!configNotSave) return;
        DeviceConfig config = form.cbDevice.getSelectedItem();
        if (config == null) {
            DialogHelper.confirm("无设备").show(this);
            return;
        }

        //检查重复绑定
        ArrayList<String> lstNativeKey = new ArrayList<>();
        HashSet<String> lstRepeat = new HashSet<>();
        for (Entry<String, String> entry : config.keyMap.buttonMap.entrySet()) {
            String nativeKey = entry.getValue();
            if (nativeKey == null) continue;
            if (lstNativeKey.contains(nativeKey)) lstRepeat.add(nativeKey);
            else lstNativeKey.add(nativeKey);
        }
        if (!lstRepeat.isEmpty()) {
            boolean ok = DialogHelper.yesNo("按键重复绑定:" + lstRepeat + "\n是否继续保存?").show(this);
            if (!ok) return;
        }

        if (form.cbXInput.isSelected()) {
            config.connectType = ConnectType.X_INPUT;
        } else if (form.cbDInput.isSelected()) {
            config.connectType = ConnectType.D_INPUT;
        } else if (form.cbHid.isSelected()) {
            config.connectType = ConnectType.HID;
        }

        config.keyMap.reset();
        viewItemEditHandler.iteratorKeyBindingItem(keyBindingItem -> config.keyMap.viewToConfig(keyBindingItem));
        config.save2();
        switchConfigEdited(false);
        form.btnDelete.setVisible(true);
        log.info("saved");
    }

    private class JoyEventHandler implements JoyEventListener {
        @Override
        public KeyMapConfig getKeyMapConfig() {
            if (lastSelectedConfig == null) return null;
            return lastSelectedConfig.keyMap;
        }

        @Override
        public void onJoyEvent(NativeEvent nativeEvent, StandardEvent standardEvent) {
            log.info("nativeEvent={}", nativeEvent);
            try {
                //更新状态
                boolean online = nativeEvent.hr == HResult.S_OK.val;
                if (lastSelectedConfig != null) lastSelectedConfig.online = online;
                //设备状态
                StringBuilder sb = new StringBuilder();
                if (!HResult.succeeded(nativeEvent.hr)) {
                    sb.append(HResult.getErrMsg(nativeEvent.hr));
                } else {
                    //原始按键
                    for (NativeAction currentPress : nativeEvent.currentPress) {
                        NativeKey nativeKey = currentPress.nativeKey;
                        sb.append(nativeKey.key);
                        if (nativeKey.type != KeyType.BTN) sb.append(':').append(nativeKey.nativeValue);
                        sb.append('\n');
                    }
                    //标准按键
                    sb.append('\n');
                    for (StandardAction currentPress : standardEvent.currentPress) {
                        StandardKey standardKey = currentPress.standardKey;
                        sb.append(standardKey.key);
                        if (standardKey.type == KeyType.STICK) sb.append(':').append(standardKey.range);
                        sb.append('\n');
                    }
                }
                form.txtStateInfo.setText(sb.toString());

                //绑定按键
                if (currentBindingItem != null) {
                    if (!online) {
                        //突然掉线,取消输入
                        currentBindingItem.cancelEdit();
                        currentBindingItem = null;
                    } else if (!nativeEvent.currentPress.isEmpty()) {
                        boolean bound = currentBindingItem.onBound(nativeEvent.currentPress.get(0).nativeKey);
                        if (bound) {
                            if (lastSelectedConfig != null) {
                                lastSelectedConfig.keyMap.viewToConfig(currentBindingItem);//马上生效配置
                            }
                            currentBindingItem = null;
                            switchConfigEdited(true);
                        }
                        //false时:按了类型不符的key
                    }
                }
            } catch (Throwable e) {
                onExecuteException("按键处理异常", e);
            }
        }
    }

    private class ViewItemEditHandler implements KeyBindingListener, KeyEventPostProcessor {

        private void initKeyPanel() {
            form.pKey.removeAll();
            int[] childCountBuff = new int[2];
            //前方按钮
            addStandardKeyPanel(childCountBuff, true, KeyBindingItem.standard(StandardKeys.L2, this));
            addStandardKeyPanel(childCountBuff, true, KeyBindingItem.standard(StandardKeys.L1, this));
            addStandardKeyPanel(childCountBuff, false, KeyBindingItem.standard(StandardKeys.R2, this));
            addStandardKeyPanel(childCountBuff, false, KeyBindingItem.standard(StandardKeys.R1, this));
            //左右两侧按钮
            addStandardKeyPanel(childCountBuff, true, KeyBindingItem.standard(StandardKeys.BINDING_ARROW, this));
            JPanel pButton = new JPanel(new GridLayout(0, 1));
            pButton.add(KeyBindingItem.standard(StandardKeys.X, this));
            pButton.add(KeyBindingItem.standard(StandardKeys.Y, this));
            pButton.add(KeyBindingItem.standard(StandardKeys.A, this));
            pButton.add(KeyBindingItem.standard(StandardKeys.B, this));
            addStandardKeyPanel(childCountBuff, false, pButton);
            //中间区域
            addStandardKeyPanel(childCountBuff, true, KeyBindingItem.standard(StandardKeys.SELECT, this));
            addStandardKeyPanel(childCountBuff, false, KeyBindingItem.standard(StandardKeys.START, this));
            //两个手柄
            addStandardKeyPanel(childCountBuff, true, KeyBindingItem.standard(StandardKeys.L3, this));
            addStandardKeyPanel(childCountBuff, false, KeyBindingItem.standard(StandardKeys.R3, this));
            addStandardKeyPanel(childCountBuff, true, KeyBindingItem.standard(StandardKeys.BINDING_STICK_LX, this));
            addStandardKeyPanel(childCountBuff, true, KeyBindingItem.standard(StandardKeys.BINDING_STICK_LY, this));
            addStandardKeyPanel(childCountBuff, false, KeyBindingItem.standard(StandardKeys.BINDING_STICK_RX, this));
            addStandardKeyPanel(childCountBuff, false, KeyBindingItem.standard(StandardKeys.BINDING_STICK_RY, this));
        }

        /**
         * 添加标准按键组件
         *
         * @param childCountBuff 用于临时存储左右组件的数量,长度为2
         */
        private void addStandardKeyPanel(int[] childCountBuff, boolean isLeft, Component toAdd) {
            GridBagConstraints gbc = new GridBagConstraints();
            gbc.gridx = isLeft ? 0 : 1;
            gbc.gridy = childCountBuff[isLeft ? 0 : 1]++;
            gbc.weightx = 1.0;
            gbc.anchor = GridBagConstraints.CENTER;
            gbc.fill = GridBagConstraints.HORIZONTAL;
            //log.info("isLeft={}, x,y={},{}", isLeft, gbc.gridx, gbc.gridy);
            form.pKey.add(toAdd, gbc);
        }

        private void addCustomKeyPanel(String standardKey, NativeKey nativeKey) {
            KeyBindingItem toAdd = KeyBindingItem.custom(StandardKey.button(standardKey), this);
            toAdd.onBound(nativeKey);
            int count = form.pKey.getComponentCount();
            GridBagConstraints gbc = new GridBagConstraints();
            gbc.gridx = count % 2;
            gbc.gridy = count / 2;
            gbc.weightx = 1.0;
            gbc.anchor = GridBagConstraints.CENTER;
            gbc.fill = GridBagConstraints.HORIZONTAL;
            //log.info("standardKey={}, x={}, y={}", standardKey, gbc.gridx, gbc.gridy);
            form.pKey.add(toAdd, gbc);
        }

        private void addCustomKey() {
            String input = JOptionPane.showInputDialog(JoySettingFrame.this, "", "请输入按键名", JOptionPane.PLAIN_MESSAGE);
            if (!TextUtil.isBlank(input)) {
                log.info("input={}", input);
                switchConfigEdited(true);
                iteratorKeyBindingItem(item -> {
                    if (item.getStandardKey().key.equals(input)) throw new AppCommonException("已存在按键:" + input);
                });
                addCustomKeyPanel(input, null);
                ComponentUtil.afterChangedChild(form.pKey);
                ComponentUtil.scrollPaneTo(form.spKey, false, null);
            }
        }

        @Override
        public void onClickBinding(KeyBindingItem item) {
            if (currentBindingItem != null || lastSelectedConfig == null || !lastSelectedConfig.online) return;
            currentBindingItem = item;
            item.onStartBinding();
        }

        @Override
        public void onConfirmDeleteKey(KeyBindingItem item) {
            log.info("key={}", item.getStandardKey());
            //移除该项
            int index = form.pKey.getComponentZOrder(item);
            form.pKey.remove(index);
            //重排列后面的项目:只更新gbc
            int size = form.pKey.getComponentCount();
            GridBagLayout layout = (GridBagLayout) form.pKey.getLayout();
            for (int i = index; i < size; i++) {
                Component afterItem = form.pKey.getComponent(i);
                GridBagConstraints gbc = layout.getConstraints(afterItem);
                //log.info("afterItem={}, [{},{}]", ((KeyBindingItem) afterItem).getStandardKey(), gbc.gridx, gbc.gridy);
                if (gbc.gridx == 0) gbc.gridy -= 1;//y:如果原x是左边,则y上移一行
                gbc.gridx = (gbc.gridx + 1) % 2;//x:[0,1]交换
                layout.setConstraints(afterItem, gbc);
            }
            if (lastSelectedConfig != null) lastSelectedConfig.keyMap.buttonMap.remove(item.getStandardKey().key);
            switchConfigEdited(true);
            ComponentUtil.afterChangedChild(form.pKey);
        }

        private void iteratorKeyBindingItem(Runner1<KeyBindingItem> runner) {
            for (int i = 0; i < form.pKey.getComponentCount(); i++) {
                Component c = form.pKey.getComponent(i);
                if (c instanceof KeyBindingItem) {
                    runner.run((KeyBindingItem) c);
                } else if (c instanceof JPanel) {//AB XY
                    JPanel p = (JPanel) c;
                    for (int j = 0; j < p.getComponentCount(); j++) {
                        runner.run((KeyBindingItem) p.getComponent(j));
                    }
                }
            }
        }

        /**
         * 全局监听[Esc,删除键]
         */
        @Override
        public boolean postProcessKeyEvent(KeyEvent e) {
            switch (e.getKeyCode()) {
                case KeyEvent.VK_ESCAPE://还原
                    log.debug("e={}", e);
                    if (currentBindingItem != null) {
                        currentBindingItem.cancelEdit();
                        currentBindingItem = null;
                    }
                    return true;
                case KeyEvent.VK_BACK_SPACE://置空
                    log.debug("e={}", e);
                    if (currentBindingItem != null) {
                        currentBindingItem.onBound(null);
                        if (lastSelectedConfig != null) {
                            lastSelectedConfig.keyMap.viewToConfig(currentBindingItem);//马上生效配置
                        }
                        currentBindingItem = null;
                        switchConfigEdited(true);
                    }
                    return true;
                default:
                    return false;
            }
        }

        private void onSelectedDevice(DeviceConfig config) {
            if (config == null) return;//没有在线设备和本地配置
            if (confirmNotSaveConfig()) {
                form.cbDevice.setSelectedItem(lastSelectedConfig);
                return;
            }

            //重新读取配置
            //log.info("config.1={}", config);
            DeviceConfig configFile;
            if (config.fromFile != null && (configFile = config.readConfigFile()) != null) {
                config.connectType = configFile.connectType;
                config.keyMap = configFile.keyMap;
            } else {
                config.keyMap.reset();
            }
            //log.info("config.2={}", config);
            log.info("config={}", config.name);
            presenter.stopListenState(false);

            //连接方式
            switch (config.connectType) {
                case X_INPUT:
                    form.cbXInput.setSelected(true);
                    break;
                case D_INPUT:
                    form.cbDInput.setSelected(true);
                    break;
                case HID:
                    form.cbHid.setSelected(true);
                    break;
            }
            //显示文本
            form.txtStateInfo.setText("");
            StringBuilder sbInfo = new StringBuilder();
            sbInfo.append("guid:").append(config.guid);
            if (config.err != null) {
                sbInfo.append('\n').append(config.err.getMessage());
            }
            form.txtInfo.setText(sbInfo.toString());
            //按键
            ArrayList<KeyBindingItem> lstToRemove = new ArrayList<>();
            HashMap<String, String> buttonMap = new HashMap<>(config.keyMap.buttonMap);
            //遍历面板:移除自定义按键面板;绑定标准按键(并从temp中移除)
            iteratorKeyBindingItem(item -> {
                if (item.isCustom()) {
                    lstToRemove.add(item);//不能在循环中remove
                    return;
                }
                boolean hadBoundButton = item.configToView(config.keyMap, buttonMap);
                if (hadBoundButton) buttonMap.remove(item.getStandardKey().key);
            });

            //移除旧的自定义面板
            for (KeyBindingItem item : lstToRemove) form.pKey.remove(item);

            if (!buttonMap.isEmpty()) {//自定义按键
                ArrayList<Entry<String, String>> list = new ArrayList<>(buttonMap.entrySet());
                list.removeIf(entry -> entry.getKey() == null);
                list.sort(Entry.comparingByKey());//按键名排序
                for (Entry<String, String> entry : list) {
                    String standardKey = entry.getKey();
                    String nativeKeyStr = entry.getValue();
                    if (TextUtil.isBlank(nativeKeyStr)) {
                        addCustomKeyPanel(standardKey, null);
                    } else {
                        addCustomKeyPanel(standardKey, NativeKey.button(nativeKeyStr, 0));
                    }
                }
            }

            lastSelectedConfig = config;
            switchConfigEdited(false);
            form.btnDelete.setVisible(config.fromFile != null);
            log.info("配置加载成功={}, online={}", config.name, config.online);
            //启动监听
            if (config.online) {
                log.info("开始监听={}", config.name);
                presenter.startListenState(config.guid, joyEventHandler);
            }
        }

    }
}
